import re
import time
import Image
import random
import string
import os.path
import pyautogui
import pyperclip
from time import sleep
from datetime import datetime
from selenium import webdriver
from lib.data.pic_data import *
from common.decs import relations
from tools.class_excel import Reader
from tools.class_mysql import Conn
from tools.logger import Logger
from selenium.webdriver.common.by import By
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.common.action_chains import ActionChains as ac


class Web:

    def __init__(self, writer):
        """实例变量"""
        self.driver: webdriver.Chrome = None
        self.relations = {}  # 存储关联数据
        self.writer = writer
        self.reader = Reader()
        self.r = 0  # 记录读取数据的行号
        self.logger = Logger()
        self.conn = Conn("root", "diama_239", "192.168.1.239", "iconductor_media")

    def openbroswer(self, br="gc"):
        """
        打开浏览器
        :param br: 浏览器类型  gc:Chrome; ff:Firefox; ie:IE; eg:Edge
        :param no_ui: Windows系统默认为界面模式, 无界面设为:True
        :return:
        """
        if br == "ff":
            self.driver = webdriver.Firefox()
        elif br == "ie":
            self.driver = webdriver.Ie()
        elif br == "eg":
            self.driver = webdriver.Edge()
        else:
            self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(10)
        self.driver.maximize_window()

    def openurl(self, url):
        """打开被测网站"""
        self.driver.get(url)

    def __find_ele(self, locator=''):
        """
        定位元素标签并返回
        :param locator:定位表达式
        :return: 返回元素标签
        """
        self.logger.info(f"开始定位元素 {str(locator)}")
        try:
            s = string.ascii_letters
            if locator.startswith('/'):
                ele = self.driver.find_element(By.XPATH, locator)
            elif locator.startswith('[') or locator.startswith(s[s.find(locator[:1])]):
                ele = self.driver.find_element(By.CSS_SELECTOR, locator)
            else:
                ele = self.driver.find_element(By.ID, locator)
            self.logger.info(f"元素 {str(locator)} 定位成功!")
        except Exception as e:
            ele = None
            self.logger.error("元素 %s 定位失败" % str(locator))
            self.write_excel(False, "元素 %s 定位失败!" % str(locator))
        return ele

    def wait(self, locator='', time=10, poll=0.5):
        """
        显示等待
        :param locator: 需要等待的元素标签定位方式
        :param time: 最长等待时间
        :param poll: 轮询间隔时间
        :return:
        """
        self.logger.info("开始等待 %s 元素" % str(locator))
        try:
            if locator.startswith('/'):
                loc = (By.XPATH, locator)
            elif locator.startswith('[') or locator.startswith('#'):
                loc = (By.CSS_SELECTOR, locator)
            else:
                loc = (By.ID, locator)
            start = datetime.now()
            WebDriverWait(self.driver, time, poll).until(ec.visibility_of_element_located(loc))
            end = datetime.now()
            times = (end - start).seconds
            self.logger.info("元素 %s 等待成功, 耗时%d秒" % (str(locator), times))
            self.write_excel(True, '')
        except Exception as e:
            self.logger.error("元素 %s 等待失败,请确认" % str(locator))
            self.write_excel(False, "元素等待失败!")
            return None

    def click(self, locator=''):
        """
        点击操作
        :param locator: 定位表达式
        """
        try:
            ele = self.__find_ele(locator)
            if ele is not None or ele != "":
                sleep(0.5)
                ele.click()
                sleep(0.5)
                self.write_excel(True, '')
                self.logger.info("元素 %s 点击成功!" % str(locator))
        except Exception as e:
            self.write_excel(False, '点击失败!')
            self.logger.error("元素 %s 点击失败!" % str(locator))
            return

    def clear(self, locator=''):
        """清空输入框"""
        ele = self.__find_ele(locator)
        try:
            ele.clear()
            self.logger.info("输入框清空成功!")
        except Exception as e:
            self.write_excel(False, '输入框清空失败!')
            self.logger.error("输入框清空失败!")

    def get_screen_shot(self, file_name):
        """
        截图操作
        :param file_name: 图片的名称
        :return:
        """
        path = './lib/result/imgs/'+file_name+'.png'
        self.driver.get_screenshot_as_file(path)
        return path

    def cut_screen(self, locator, file_name):
        """
        截取当前页面的指定部位
        :param locator: 指定部位的元素定位方式
        :param file_name: 截取的当前页面名称
        :return:
        """
        path = self.get_screen_shot(file_name)
        path = os.path.abspath(path)
        photo = self.__find_ele(locator)
        x = photo.location['x']
        y = photo.location['y']
        width = photo.size['width']
        height = photo.size['height']
        img = Image.open(path)
        img = img.crop((x, y, x + width, y + height))
        new_path = './lib/result/imgs/new_screen.png'
        img.save(new_path)
        img_path = os.path.abspath(new_path)
        return img_path

    @relations
    def input(self, locator='', text=''):  # 'search_key','{{orderid}}'
        """
        输入操作
        :param locator: 定位表达式
        :param text: 输入的数据
        """
        ele = self.__find_ele(locator)
        try:
            if ele is not None or ele != "":
                ele.send_keys(text)
                self.write_excel(True, '')
                self.logger.info("元素 %s 输入操作成功!" % str(locator))
        except Exception as e:
            self.write_excel(False, '输入失败!')
            self.logger.error("元素 %s 输入操作失败!" % str(locator))
        return

    def slowly_input(self, locator='', value=''):
        """
        输入操作
        :param locator:元素定位方式（元组类型）
        :param value:
        :param file_name:
        :return:
        """
        ele = self.__find_ele(locator)
        try:
            ele.clear()
            if ele is not None or ele != "":
                for i in range(len(value)):
                    ele.send_keys(value[i:i+1])
                    sleep(0.5)
                self.logger.info("元素 %s 输入操作成功!" % str(locator))
                self.write_excel(True, '')
            sleep(1)
        except Exception as e:
            self.write_excel(False, '输入失败!')
            self.logger.error("元素 %s 输入操作失败!" % str(locator))
        return

    def input_time(self, locator='', text=''):
        """
        输入日期
        :param locator: 元素定位方式
        :param text: 输入的日期(正确格式为: 2023-08-25  若输入的为其它格式日期, 将转化为正确格式)
        :return:
        """
        ele = self.__find_ele(locator)
        times = ''
        year = times.find('年')
        month = times.find('月')
        day = times.find('日')
        if text == "now":
            times = datetime.now().date()
        elif re.search(r".*年.*月.*日", text):
            times = f"{times[:year]}-{times[year+1:month]}-{times[month+1:day]}"
        elif re.search(r".*/.*/.*", text):
            times = f"{time[:4]}-{time[5:7]}-{time[8:]}"
        elif re.search(r".*-.*-.*", text):
            times = text
        else:
            self.write_excel(False, "请输入正确的日期!")
            self.logger.error("请输入正确的日期!")
        try:
            if ele is not None or ele != "":
                ele.send_keys(times)
                self.write_excel(True, '')
                self.logger.info("时间 %s 输入成功!" % str(times))
        except Exception as e:
            self.write_excel(False, "输入失败!")
            self.logger.error("时间 %s 输入失败!" % str(times))

    def intoframe(self, locator=''):
        """
        切入frame
        :param locator: 定位表达式
        """
        ele = self.__find_ele(locator)
        if ele is not None or ele != "":
            self.driver.switch_to.frame(ele)
            self.write_excel(True, '')
            self.logger.info("切入frame%s成功" % str(locator))
        else:
            self.write_excel(False, '切入frame失败!')
            self.logger.info("切入frame%s失败" % str(locator))

    def outoframe(self):
        """切出frame"""
        self.driver.switch_to.default_content()
        self.write_excel(True, '')
        self.logger.info('frame切出操作成功!')

    def select(self, locator='', visible_text=''):
        """
        下拉框选择
        :param locator: 定位表达式
        :param visible_text: 选择的文本值
        """
        ele = self.__find_ele(locator)
        select = Select(ele)
        select.select_by_visible_text(visible_text)

    def switchwin(self, idx=-1):
        """
        切换浏览器窗口
        :param idx: 窗口下标
        """
        handles = self.driver.window_handles
        self.driver.switch_to.window(handles[idx])
        sleep(1)
        self.write_excel(True, '')
        self.logger.info('窗口切换操作完成!')

    def back(self):
        """返回上一页面"""
        self.driver.back()
        sleep(0.5)
        self.write_excel(True, '')

    def quit(self):
        """退出浏览器"""
        self.driver.quit()
        self.write_excel(True, '')

    def refresh(self):
        """浏览器刷新"""
        self.driver.refresh()
        self.write_excel(True, '')
        sleep(1)

    def open_new_win(self, url):
        """打开新的页面"""
        self.driver.execute_script(f"window.open('{url}', 'new window')")
        sleep(1)

    def close(self):
        """关闭当前浏览器窗口"""
        self.driver.close()
        self.write_excel(True, '')
        sleep(1)

    def gettext(self, locator='', datakey='', reg=''):
        """
        获取指定元素标签的文本数据
        :param locator: 定位表达式
        :return: 文本值
        """
        """
        orderid = '订单号：  202205270910581349    |     付款金额（元）：  1699.00 元'
        写法1:
        # reg = '\d{18}'   # 提取单个数字直接用\d,  若要提取连续n个数字  \d{n}
        写法2:
        # reg = "订单号：  (.*?)    |"     # (.*?)  万能正则表达式
        """
        ele = self.__find_ele(locator)
        if ele is not None or ele != '':
            try:
                text = ele.text
                # print(text)
                # 是否存在正则表达式
                if reg:
                    text = re.findall(reg, text)[0]

                # 保存关联数据
                self.relations[datakey] = text
                # print(self.relations)
                self.write_excel(True, "文本为：%s" % text)
            except Exception as e:
                text = None
                self.write_excel(False, "元素文本值获取失败!")
            return text

    @relations
    def getattribute(self, locator='', attribute_name=''):
        """
        获取指定元素标签指定属性值
        :param locator: 定位表达式
        :param attribute_name: 属性名称
        :return:
        """
        ele = self.__find_ele(locator)
        if ele is not None or ele != '':
            try:
                attr_value = ele.get_attribute(attribute_name)
                # 保存关联数据
                self.relations[attribute_name] = attr_value
                self.write_excel(True, "属性为：%s" % attr_value)
            except Exception as e:
                attr_value = None
                self.write_excel(False, "元素属性值获取失败!")
            return attr_value

    def remattribute(self, locator='', attribute_name=''):
        """
        移除指定元素的属性
        :param locator:
        :param attribute_name: 需要移除的属性名
        :return:
        """
        ele = self.__find_ele(locator)
        if ele is not None or ele != '':
            try:
                js = "arguments[0].removeAttribute('%s')" % attribute_name
                self.executejs(js, ele)
                self.write_excel(True, "")
                self.logger.info("成功移除元素 %s 属性" % str(locator))
            except Exception as e:
                self.write_excel(False, "属性%s移除失败!" % attribute_name)
                self.logger.error("元素 %s 属性移除失败!" % str(locator))

    def gettile(self):
        """获取网页title信息"""
        title = self.driver.title
        return title

    def executejs(self, js, ele=None):
        """执行js脚本"""
        self.driver.execute_script(js, ele)
        self.logger.info("js执行成功!")

    def jsclick(self, locator=''):
        """js点击操作"""
        ele = self.__find_ele(locator)
        js = "arguments[0].click();"  # arguments[0] 表示执行js语句时,传入第一个元素标签对象
        self.executejs(js, ele)

    def upload_file(self, locator='', path=''):
        """文件上传"""
        try:
            ele = self.__find_ele(locator)
            if ele is not None or ele != "":
                try:
                    path = os.path.abspath(path)
                    # print(path)
                    ele.send_keys(path)
                    self.write_excel(True, "")
                    self.logger.info("文件%s上传成功!" % str(path))
                except Exception as e:
                    self.write_excel(False, '文件上传失败')
                    self.logger.error("文件%s上传失败,请确认!" % str(path))
        except Exception as e:
            self.logger.error("input标签定位失败, 请确认!")
            self.write_excel(False, "input标签定位失败!")

    def auto_upload(self, locator='', screen_name='', upload_name=''):
        """
        点击系统文件选择器上传文件
        :param locator: 上传图标定位方式
        :param screen_name: 页面截图名称
        :param upload_name: 要上传文件的路径及名称
        :return:
        """
        try:
            for i in eval(upload_name):
                img_path = self.cut_screen(locator, screen_name)  # 获取保存的上传文件图标路径
                pyperclip.copy(i)      # 复制要上传图片的路径
                image_loc = pyautogui.locateOnScreen(img_path, grayscale=True)   # 定位上传图标在页面的位置
                center = pyautogui.center(image_loc)    # 图标中心位置获取
                pyautogui.click(center)   # 点击中心位置
                sleep(1)
                pyautogui.hotkey('ctrl', 'v')   # 在系统文件选择弹出中粘贴复制的路径
                pyautogui.press('enter', presses=2)   # 点击回车
                sleep(2)
                self.write_excel(True, "")
                self.logger.info("文件%s上传成功!" % str(upload_name))
        except Exception as e:
            self.write_excel(False, '文件上传失败')
            self.logger.error("文件%s上传失败,请确认!")

    def suspend_loc(self, locator1='', locator2=''):
        """
        悬浮定位元素并点击
        :param locator1: 悬浮框
        :param locator2: 悬浮框内元素
        :return:
        """
        ele1 = self.__find_ele(locator1)   # 定位悬浮框
        if ele1 is not None or ele1 != '':
            ac(self.driver).move_to_element(ele1).perform()  # 鼠标悬浮
            self.sleep()
            self.__find_ele(locator2).click()  # 定位框内元素并点击
            self.sleep()
            self.logger.info("悬浮定位点击成功!")
            self.write_excel(True, '')
            return

    def down_scrollbar(self):
        """
        控制浏览器滚动条缓慢下拉到最低
        :return:
        """
        # 获取当前窗口高度
        js = "return action=document.body.scrollHeight"
        # 初始化现在滚动条所在的高度为0
        height = 0
        new_height = self.driver.execute_script(js)

        while height < new_height:
            # 将滚动条调整至页面底部
            for i in range(height, new_height, 100):
                self.driver.execute_script('window.scrollTo(0, {})'.format(i))
                time.sleep(0.1)
            height = new_height
            time.sleep(1)
            new_height = self.driver.execute_script(js)

    def embed_scrollbar(self, locator=''):
        """
        操作浏览器内嵌的滚动条
        :param locator: 滚动条内元素定位方式(元组类型)
        :return:
        """
        for i in range(2):
            rand = random.uniform(1, 4)
            time.sleep(rand)
            ele = self.__find_ele(locator)
            if self.executejs("arguments[0].scrollIntoView();", ele):
                self.write_excel(False, "")
                break
            else:
                self.write_excel(True, "")
                continue

    def execute_mysql(self, sql='', num='all'):
        """执行sql语句"""
        try:
            self.conn.execute(sql, num)
            self.write_excel(True, "sql执行成功!")
            self.logger.info("sql执行成功!")
        except Exception as e:
            self.write_excel(False, "sql执行失败!")
            self.logger.error("sql执行失败!")

    def sleep(self, t='1'):
        t = int(t)
        sleep(t)
        self.write_excel(True, '')

    @relations
    def assert_contain(self, result='', expect=''):
        """断言实际结果中包含期望的字符串数据"""
        if result.__contains__(expect):
            self.write_excel(True, "预期与实际结果一致, 通过!")
            self.logger.info(f"预期结果是:{expect} 实际结果是:{result} 实际结果包含预期字符串数据, 测试用例执行成功!")
            return True
        else:
            self.write_excel(False, "预期与实际结果不一致, 失败!")
            self.logger.error(f"预期结果是:{expect} 实际结果是:{result} 实际结果不包含预期字符串数据, 测试用例执行失败!")
            return False

    @relations
    def assert_equals(self, result='', expect=''):
        """断言实际结果与预期完全一致"""
        if result == expect:
            self.write_excel(True, "预期与实际一致, 通过!")
            self.logger.info(f"预期结果是:{expect} 实际结果是:{result} 预期与实际一致, 测试用例执行成功!")
            return True
        else:
            self.write_excel(False, "预期与实际不一致, 失败!")
            self.logger.error(f"预期结果是:{expect} 实际结果是:{result} 预期与实际不一致, 测试用例执行失败!")
            return False

    def write_excel(self, status, msg):
        """
        反向将关键字函数执行结果写入excel中
        :param status: 执行的状态
        :param msg: 实际结果
        :return:
        """
        if status:
            self.writer.write(self.r, 7, "PASS", "3")
        else:
            self.writer.write(self.r, 7, "FAIL", "2")
        if len(msg) > 1024:
            msg = msg[:1024]
        self.writer.write(self.r, 8, msg)

    def open_excel(self, excel):
        """
        打开指定的excel文件, 并创建副本
        :param excel: excel名称
        :return:
        """
        excel_name = os.path.abspath(f'./lib/case/{excel}.xlsx')
        copy_excel = os.path.abspath(f'./lib/result/{excel}-result.xlsx')
        self.reader.open_excel(excel_name)
        self.writer.copy_open(excel_name, copy_excel)

    def get_sheet(self):
        """
        获取要执行的sheet名称
        :return:
        """
        sheetnames = self.writer.get_sheets()
        sheet_index = input(f"当前sheet列表为: {sheetnames}\n1.选择列表对应下标执行对应页面的用例\n2.若不输入, 则默认执行所有用例\n请选择要执行的元素下标: ")
        if sheet_index == "":
            sheets = sheetnames
            return sheets
        else:
            if sheet_index.isdigit():
                index = eval(sheet_index)
                if index in range(len(sheetnames)):
                    if index == 0:
                        sheets = sheetnames[:1]
                    else:
                        sheets = sheetnames[:1] + sheetnames[index:index+1]
                    return sheets
                else:
                    print("请输入正确的下标!")
            else:
                print("请输入正确的下标!")

    @relations
    def get_lines(self):
        """
        读取excel中的数据, 并以list格式返回
        :return:
        """
        sheets = self.get_sheet()
        lines = {}
        for sheet in sheets:
            self.reader.set_sheet(sheet)
            lines[sheet] = self.reader.readline()
        return lines








